#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include "trie.c"
#ifdef _OPENMP
#include <omp.h>
#define NTHREADS 4
#endif

#define WORD_SIZE 6 //fgets ira ler ate no maximo 5 caracteres

char charRand(void);
void removeEOL (char*);

int main(int argc, char *argv[])
{
	Trie* shards[5];
	shards[0] = trie_new();
	shards[1] = trie_new();
	shards[2] = trie_new();
	shards[3] = trie_new();
	shards[4] = trie_new();
	void* valor = (void*)1;
	char aux[100];
	int num = 0;
	FILE* fpin;
	long num1, num2, num3, num4, num5;
	num1 = num2 = num3 = num4 = num5 = 0;
	int rank, rc;
	
	//Abre o arquivo filtrado
	if ((fpin = fopen("palavrasFiltrado.txt", "r")) == NULL)
	{
		printf("Houve um erro ao abrir o arquivo\n");
	}
	
	double arq0 = MPI_Wtime();
	//Carrega os fragmentos na estrutura Trie
	while (1)
	{
		if (fgets(aux, WORD_SIZE, fpin) == NULL)
		{
			if (feof(fpin))
			{
				break;
			}
			else
			{
				printf("Erro de leitura do arquivo\n");
				return -1;
			}
		}
		
		removeEOL(aux);
		
		//Carrega fragmentos em cada uma das estruturas Trie, de acordo com seu tamanho, e apenas se não existirem anteriormente
		//Dessa maneira, diminui a concorrência da utilização de cada Trie
		if (strlen(aux) == 1)
		{
			if (!trie_lookup(shards[0], aux))
			{
				trie_insert(shards[0], aux, valor);
				num1++;
			}
		} 
		else if (strlen(aux) == 2) 
		{
			if (!trie_lookup(shards[1], aux))
			{
				trie_insert(shards[1], aux, valor);
				num2++;
			}
		}
		else if (strlen(aux) == 3) 
		{
			if (!trie_lookup(shards[2], aux))
			{
				trie_insert(shards[2], aux, valor);
				num3++;
			}
		}
		else if (strlen(aux) == 4) 
		{
			if (!trie_lookup(shards[3], aux))
			{
				trie_insert(shards[3], aux, valor);
				num4++;
			}
		}
		else if (strlen(aux) == 5) 
		{
			if (!trie_lookup(shards[4], aux))
			{
				trie_insert(shards[4], aux, valor);
				num5++;
			}
		}
	}
	double arqf = MPI_Wtime();
	
	// MPI_Init(&argc, &argv)
	// Inicializa uma sessão MPI. 	
	rc = MPI_Init(&argc, &argv);
	if (rc == MPI_SUCCESS)
	{
		//Obtem ID do processo na variável rank	
		MPI_Comm_rank(MPI_COMM_WORLD, &rank);
		
		//Pega numero de processos que irao rodar para fazer a distribuicao de tarefas
		int nProc;
		MPI_Comm_size(MPI_COMM_WORLD, &nProc);
		
		if (rank != 0)
		{
			printf("Node %d running...\n", rank);
		}
	
		//No que ira gerar fragmentos aleatorios de tamanho 1, 2 e 3
		if (rank == 1)
		{
			//Variaveis
			char frag[4];
			
			int source, tag=1;
			MPI_Status status;
			MPI_Request handle;
			
			//Inicialização da seed do gerador aleatório
			srand48(time(NULL) - rank);
			
			//Abertura da região paralela. Cada thread irá gerar fragmentos de um tamanho
			#pragma omp parallel private(frag)
			{
				//Gera fragmentos de tamanho 1
				if (omp_get_thread_num() == 0)
				{
					while (trie_num_entries(shards[0]) > 0)
					{
						//Gera o fragmento
						sprintf(frag, "%c", charRand());
						//Caso seja um fragmento da lista a ser gerado, remove da lista
						if (trie_lookup(shards[0],frag))
						{
							trie_remove(shards[0],frag);
						}
					}
				}
				//Gera fragmentos de tamanho 2
				if (omp_get_thread_num() == 1)
				{
					while (trie_num_entries(shards[1]) > 0)
					{
						sprintf(frag, "%c%c", charRand(),charRand());
						if (trie_lookup(shards[1],frag))
						{
							trie_remove(shards[1],frag);
						}
					}
				}
				//Gera fragmentos de tamanho 3
				if (omp_get_thread_num() == 2)
				{
					while (trie_num_entries(shards[2]) > 0)
					{
						sprintf(frag, "%c%c%c", charRand(),charRand(),charRand());
						if (trie_lookup(shards[2],frag))
						{
							trie_remove(shards[2],frag);
						}
					}
				}
			}
			//Quando o nó encontra todas os fragmentos, avisa o nó mestre e finaliza a execução
			MPI_Isend("f\0", 2, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &handle);
			printf("Node %d done!\n",rank);
		}
		
		//No gerente gerador de fragmentos de tamanho 4. Recebe fragmentos gerados pelos nós escravos e coordena a execução dos mesmos
		if (rank == 2) 
		{
			//Variáveis
			char msg[5];
			MPI_Request handle;
			MPI_Status status;
			
			//Enquanto não receber todos os fragmentos, executa
			while (trie_num_entries(shards[3]) > 0)
			{
				//Espera por um fragmento gerado por um nó escravo
				MPI_Recv(msg, 5, MPI_CHAR, MPI_ANY_SOURCE, 4, MPI_COMM_WORLD, &status);
				
				//Caso o fragmento estiver na lista a ser gerado, remove da lista
				if (trie_lookup(shards[3], msg))
				{
					trie_remove(shards[3], msg);
				}
			}
			
			//Depois que todos os fragmentos foram recebidos, avisar nós escravos que não é necessário continuar executando
			int z;
			for (z = 3 ; z <=5 ; z++)
			{
				MPI_Isend("f\0", 2, MPI_CHAR, z, 0, MPI_COMM_WORLD, &handle);
			}
			
			//Avisa o nó mestre que o gerador de fragmentos encontrou todos os fragmentos
			MPI_Isend("f\0", 2, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &handle);
			printf("Node %d done!\n",rank);
		}
		
		//Nos escravos que irao gerar fragmentos aleatorios de tamanho 4
		if ((rank >= 3) && (rank <= 5))
		{
			//Variáveis
			char* frags[200];
			char msg[2];
			int flag;
			flag = 0;
			MPI_Request handle;
			MPI_Status status;
			
			//Inicialização da seed
			srand48(time(NULL) - rank);
			
			//Recebe com receive nao bloqueante o sinal de parada do gerente
			MPI_Irecv(msg, 2, MPI_CHAR, 2, 0, MPI_COMM_WORLD, &handle);
			
			while (trie_num_entries(shards[3]) > 0)
			{
				//Gera o fragmento aleatorio
				char frag[5];
				sprintf(frag,"%c%c%c%c", charRand(),charRand(),charRand(),charRand());
				
				//Para nao sobrecarregar a rede de comunicacao com mensagens completamente inuteis, faz uma filtragem previa
				while(!trie_lookup(shards[3], frag))
				{
					sprintf(frag,"%c%c%c%c", charRand(),charRand(),charRand(),charRand());
				}
				
				//Quando encontra um fragmento que ainda nao foi gerado, envia para o no gerente
				MPI_Send(frag, 5, MPI_CHAR, 2, 4, MPI_COMM_WORLD);
				
				
				//Remove o fragmento gerado da lista para nao gerar novamente

				trie_remove(shards[3], frag);
				
				//Testa se o gerente enviou uma ordem de finalização
				MPI_Test(&handle, &flag, &status);
				
				//Caso o sinal de parada for recebido, finalizar execucao
				if (flag == 1)
				{
					break;
				}
			}
			printf("Node %d done!\n",rank);
		}
		
		//No gerente gerador de fragmentos de tamanho 5. Recebe fragmentos gerados pelos nós escravos e coordena a execução dos mesmos
		if (rank == 6) 
		{
			//Variáveis
			char msg[6];
			MPI_Request handle;
			MPI_Status status;
			
			//Enquanto não receber todos os fragmentos, executa
			while (trie_num_entries(shards[4]) > 0)
			{
				//Espera por um fragmento gerado por um nó escravo
				MPI_Recv(msg, 6, MPI_CHAR, MPI_ANY_SOURCE, 5, MPI_COMM_WORLD, &status);
				
				//Caso o fragmento estiver na lista a ser gerado, remove da lista
				if (trie_lookup(shards[4], msg))
				{
					trie_remove(shards[4], msg);
				}
			}
			
			//Depois que todos os fragmentos foram recebidos, avisar nós escravos que não é necessário continuar executando
			int z;
			for (z = 7 ; z <= 16 ; z++)
			{
				MPI_Isend("f\0", 2, MPI_CHAR, z, 0, MPI_COMM_WORLD, &handle);
			}
			
			//Avisa o nó mestre que o gerador de fragmentos encontrou todos os fragmentos
			MPI_Isend("f\0", 2, MPI_CHAR, 0, 0, MPI_COMM_WORLD, &handle);
			printf("Node %d done!\n",rank);
		}
		
		//Nos que irao gerar fragmentos aleatorios de tamanho 5
		if ((rank >= 7) && (rank <= 16))
		{
			//Variáveis
			char msg[2];
			int flag = 0;
			char* frags[200];
			MPI_Request handle;
			MPI_Status status;
			
			//Inicialização da seed
			srand48(time(NULL) - rank);
			
			//Recebe com receive nao bloqueante o sinal de parada do gerente
			MPI_Irecv(msg, 2, MPI_CHAR, 6, 0, MPI_COMM_WORLD, &handle);
			while (trie_num_entries(shards[4]) > 0)
			{
				//Gera o fragmento aleatorio
				char frag[6];
				sprintf(frag,"%c%c%c%c%c", charRand(),charRand(),charRand(),charRand(), charRand());
				
				//Para nao sobrecarregar a rede de comunicacao com mensagens completamente inuteis, faz uma filtragem previa
				while(!trie_lookup(shards[4], frag))
				{
					sprintf(frag,"%c%c%c%c%c", charRand(),charRand(),charRand(),charRand(), charRand());
				}
				
				//Quando encontra um fragmento que ainda nao foi gerado, envia para o no gerente
				MPI_Send(frag, 6, MPI_CHAR, 6, 5, MPI_COMM_WORLD);
				
				//Espera todas as threads gerarem os fragmentos
				
				//Remove o fragmento gerado da lista para nao gerar novamente
				trie_remove(shards[3], frag);
				
				//Testa se o gerente enviou uma ordem de finalização
				MPI_Test(&handle, &flag, &status);
				
				//Caso o sinal de parada for recebido, finalizar execucao
				if (flag == 1)
				{
					break;
				}
			}
			printf("Node %d done!\n",rank);
		}
		
		//No mestre do sistema
		if (rank == 0)
		{
			int i;
			char msg[6];
			
			int source, tag=1;
			MPI_Status status;
			MPI_Request handle;
			
			double t0 = MPI_Wtime();
			//Recebe mensagens que dizem que já foram gerados todos os fragmentos
			MPI_Recv(msg, 2, MPI_CHAR, 1, 0, MPI_COMM_WORLD, &status);
			MPI_Recv(msg, 2, MPI_CHAR, 2, 0, MPI_COMM_WORLD, &status);
			MPI_Recv(msg, 2, MPI_CHAR, 6, 0, MPI_COMM_WORLD, &status);
			double tf = MPI_Wtime();
			
			printf("Tam:\t\tN. Frags\n1..:\t\t%ld\n2..:\t\t%ld\n5..:\t\t%ld\n4..:\t\t%ld\n5..:\t\t%ld\n", num1, num2, num3, num4, num5);
			printf("Tempo de processamento do arquivo: %lf\n", (arqf - arq0));
			printf("Tempo para gerar os fragmentos: %lf\n", (tf - t0));
		}
	}
	MPI_Finalize();
	fclose(fpin);
	return 0;
}

char charRand()
{
	return (char)((lrand48()%26) + 97);
}

void removeEOL (char* str)
{
	int i;
	for (i = 0 ; i < strlen(str) ; i++)
	{
		if (str[i] == '\n')
		{
			str[i] = '\0';
		}
	}	
}